/*
 *   Copyright 2021 zzh
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 *
 */

package com.aduib.boot.common.io;

import com.aduib.boot.common.exception.IoException;
import com.aduib.boot.common.util.CharsetUtils;

import java.io.IOException;
import java.io.InputStream;
import java.io.PushbackInputStream;

/**
 * @description: BOMInputStream
 * @author: zzh
 * @date: 2021/10/28 11:02
 */
public class BOMInputStream extends InputStream {
  private static final int BOM_SIZE = 4;
  private final PushbackInputStream in;
  private final String defaultCharset;
  private boolean isInited = false;
  private String charset;

  // ----------------------------------------------------------------- Constructor start
  public BOMInputStream(InputStream in) {
    this(in, CharsetUtils.UTF_8);
  }

  public BOMInputStream(InputStream in, String defaultCharset) {
    this.in = new PushbackInputStream(in, BOM_SIZE);
    this.defaultCharset = defaultCharset;
  }
  // ----------------------------------------------------------------- Constructor end

  /**
   * 获取默认编码
   *
   * @return 默认编码
   */
  public String getDefaultCharset() {
    return defaultCharset;
  }

  /**
   * 获取BOM头中的编码
   *
   * @return 编码
   */
  public String getCharset() {
    if (!isInited) {
      try {
        init();
      } catch (IOException ex) {
        throw new IoException(ex);
      }
    }
    return charset;
  }

  @Override
  public void close() throws IOException {
    isInited = true;
    in.close();
  }

  @Override
  public int read() throws IOException {
    isInited = true;
    return in.read();
  }

  /**
   * Read-ahead four bytes and check for BOM marks. <br>
   * Extra bytes are unread back to the stream, only BOM bytes are skipped.
   *
   * @throws IOException 读取引起的异常
   */
  protected void init() throws IOException {
    if (isInited) {
      return;
    }

    byte[] bom = new byte[BOM_SIZE];
    int n, unread;
    n = in.read(bom, 0, bom.length);

    if ((bom[0] == (byte) 0x00)
        && (bom[1] == (byte) 0x00)
        && (bom[2] == (byte) 0xFE)
        && (bom[3] == (byte) 0xFF)) {
      charset = "UTF-32BE";
      unread = n - 4;
    } else if ((bom[0] == (byte) 0xFF)
        && (bom[1] == (byte) 0xFE)
        && (bom[2] == (byte) 0x00)
        && (bom[3] == (byte) 0x00)) {
      charset = "UTF-32LE";
      unread = n - 4;
    } else if ((bom[0] == (byte) 0xEF) && (bom[1] == (byte) 0xBB) && (bom[2] == (byte) 0xBF)) {
      charset = "UTF-8";
      unread = n - 3;
    } else if ((bom[0] == (byte) 0xFE) && (bom[1] == (byte) 0xFF)) {
      charset = "UTF-16BE";
      unread = n - 2;
    } else if ((bom[0] == (byte) 0xFF) && (bom[1] == (byte) 0xFE)) {
      charset = "UTF-16LE";
      unread = n - 2;
    } else {
      // Unicode BOM mark not found, unread all bytes
      charset = defaultCharset;
      unread = n;
    }
    // System.out.println("read=" + n + ", unread=" + unread);

    if (unread > 0) {
      in.unread(bom, (n - unread), unread);
    }

    isInited = true;
  }
}
